﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;

using iStudio.Structure.Engine;

namespace iStudio.Structure.Wave
{
    public class WaveTrack : INotifyPropertyChanged, IOutputProvider
    {
        private WaveFormat _waveFormat;
        private readonly ObservableCollection<WaveChunk> _trackChunks;
        private readonly OutputSampleProvider _output; 

        private long _length;

        #region Constructors

        /// <summary>
        /// Constructor creates new track
        /// </summary>
        /// <param name="waveFormat"></param>
        public WaveTrack (WaveFormat waveFormat)
        {
            _waveFormat = new WaveFormat (waveFormat);
            Volume = 1.0f;

            _trackChunks = new ObservableCollection<WaveChunk>();
            _output = new OutputSampleProvider();
            
            OutputChannel = WaveChannel.SPEAKER_AUTO;

            CreateChunks ();
        }

        /// <summary>
        /// >Primary constructor creates new Track with single chunk from buffer
        /// </summary>
        /// <param name="waveFormat"></param>
        /// <param name="buffer"></param>
        public WaveTrack (WaveFormat waveFormat, float[] buffer) : this (waveFormat)
        {
            ConstructChunks (buffer);
        }

        #endregion

        #region Private methods

        private void CreateChunks ()
        {
            for (int i = 0; i < WaveFormat.Channels; i++)
            {
                _trackChunks.Add(new WaveChunk(i));
            }
        }

        private void ConstructChunks (float[] buffer)
        {
            //
            // Construct chunks from full buffer, parse channels
            //

            // Two loops, but really just one iteration
            for (long i = 0; i < buffer.LongLength; i += WaveFormat.Channels)
            {
                for (int x = 0; x < WaveFormat.Channels; x++)
                {
                    _trackChunks[x].WaveBuffer.Add(buffer[i + x]);
                }
            }

            //
            // Add chunks to track
            //

            foreach (WaveChunk chunk in _trackChunks)
            {
                chunk.PropertyChanged += ChunkOnPropertyChanged;
            }
        }

        private void ChunkOnPropertyChanged (object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            NotifyPropertyChanged ("IsSelected");
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Length of track in samples
        /// </summary>
        public long Length
        {
            get
            {
                //
                // TODO: Optimize this loop
                //
                foreach (WaveChunk chunk in _trackChunks)
                {
                    if (_length < (chunk.Length + chunk.Offset) )
                    {
                        _length = (chunk.Length + chunk.Offset);
                    }
                }

                return _length;
            }
        }

        /// <summary>
        /// Track Custom Name
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Track WaveFormat
        /// </summary>
        public WaveFormat WaveFormat
        {
            get
            {
                return _waveFormat;
            }
            set
            {
                _waveFormat = value;
            }
        }

        /// <summary>
        /// Sample rate property
        /// </summary>
        public int SampleRate
        {
            get
            {
                return _waveFormat.SampleRate;
            }
            set
            {
                _waveFormat = new WaveFormat
                                  {
                                      SampleRate = value,
                                      Channels = _waveFormat.Channels
                                  };
            }
        }

        /// <summary>
        /// Channel to which we should output the track
        /// </summary>
        public WaveChannel OutputChannel
        {
            get;
            protected set;
        }

        /// <summary>
        /// Current track volume
        /// </summary>
        public float Volume
        {
            get;
            set;
        }

        /// <summary>
        /// Parts of the track to display
        /// </summary>
        public ObservableCollection<WaveChunk> TrackChunks
        {
            get
            {
                return _trackChunks;
            }
        }

        /// <summary>
        /// Returns if all track chunks are selected
        /// </summary>
        public bool IsSelected
        {
            get
            {
                bool isSelected = true;

                foreach (WaveChunk chunk in TrackChunks)
                {
                    if (!chunk.IsSelected)
                    {
                        isSelected = false;

                        break;
                    }
                }

                return isSelected;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Selects all chunks
        /// </summary>
        public void SelectAllChunks ()
        {
            foreach (WaveChunk chunk in TrackChunks)
            {
                chunk.IsSelected = true;
            }

            NotifyPropertyChanged ("IsSelected");
        }

        /// <summary>
        /// Deselects all chunks
        /// </summary>
        public void DeselectAllChunks ()
        {
            foreach (WaveChunk chunk in TrackChunks)
            {
                chunk.IsSelected = false;
            }

            NotifyPropertyChanged ("IsSelected");
        }

        /// <summary>
        /// Returns sample value from correct chunk or 0 if it is not 
        /// </summary>
        /// <param name="index"></param>
        public float GetSample (long index)
        {
            int channel = (int)(index % WaveFormat.Channels);

            int sampleIndex = (int)(index / WaveFormat.Channels);

            //
            // Search for chunk
            //

            try
            {
                foreach (WaveChunk chunk in TrackChunks)
                {
                    // Check if sample is in chunk length
                    if ((sampleIndex >= chunk.Offset) && (sampleIndex <= (chunk.Offset + chunk.Length)) && (chunk.Channel == channel))
                    {
                        //
                        // Found sample, return it
                        //

                        return chunk.WaveBuffer[sampleIndex - chunk.Offset];
                    }
                }

                return 0.0f;
            }
            catch (Exception)
            {
                return 0.0f;
            }
        }

        public void SetSample (long index, float sample)
        {
            int channel = (int)(index % WaveFormat.Channels);

            int sampleIndex = (int)(index / WaveFormat.Channels);

            //
            // Search for chunk
            //

            try
            {
                foreach (WaveChunk chunk in TrackChunks)
                {
                    // Check if sample is in chunk length
                    if ((sampleIndex >= chunk.Offset) && (sampleIndex <= (chunk.Offset + chunk.Length)) && (chunk.Channel == channel))
                    {
                        chunk.WaveBuffer[sampleIndex - chunk.Offset] = sample;

                        return;
                    }
                }
            }
            // ReSharper disable EmptyGeneralCatchClause
            catch (Exception)
            // ReSharper restore EmptyGeneralCatchClause
            {
            }
        }

        /// <summary>
        /// Displays track metadata as a string
        /// </summary>
        /// <returns>human readable string</returns>
        public override string ToString()
        {
            if (String.IsNullOrEmpty (Name))
            {
                return "Noname Track - " + WaveFormat.SampleRate + " Hz - " + WaveFormat.Channels + " channels";
            }

            return Name + " - " + WaveFormat.SampleRate + " Hz - " + WaveFormat.Channels + " channels";
        }

        #endregion      

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Change info
        /// </summary>
        /// <param name="info"></param>
        private void NotifyPropertyChanged(string info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion

        #region IOutputProvider

        public OutputSampleProvider Output
        {
            get
            {
                return _output;
            }
        }

        #endregion
    }
}
